﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using Inspire.Classes.Exceptions;

namespace Inspire.Classes.Utility
{
    public class Paginator<T>
    {
        /// <summary>
        /// Reference to result set we are going to paginate.
        /// </summary>
        private ObjectResult<T> _resultSet;

        /// <summary>
        /// Results as a list.
        /// </summary>
        private List<T> _resultList;

        /// <summary>
        /// The results for the current pageIndex.
        /// </summary>
        private LinkedList<T> _page = new LinkedList<T>();

        /// <summary>
        /// The number of results to display on each pageIndex.
        /// </summary>
        private int _pageSize;
        public int PageSize
        {
            get
            {
                return this._pageSize;
            }
        }

        /// <summary>
        /// The total number of pages required to display all items in the result set, as calculated using
        /// the specified pageIndex size. The last pageIndex may or may not contain a full pageIndex of items.
        /// </summary>
        private int _totalPages;
        public int TotalPages
        {
            get
            {
                return this._totalPages;
            }
        }

        /// <summary>
        /// The total number of results.
        /// </summary>
        private int _totalResults;
        public int TotalResults
        {
            get
            {
                return this._totalResults;
            }
        }

        /// <summary>
        /// The pageIndex currently displayed.
        /// </summary>
        private int _pageNumber;
        public int PageNumber
        {
            get
            {
                return this._pageNumber;
            }
        }

        /// <summary>
        /// Current index in the list.
        /// </summary>
        private int _listPosition;
        
        /// <summary>
        /// Create a new Paginator that returns a fixed size of results for the pageIndex specified.
        /// </summary>
        /// <param name="resultSet">(ObjectResult&lt;T&gt;) The resultSet to paginate.</param>
        public Paginator(ObjectResult<T> resultSet, int pageSize)
        {
            // Set all items to default
            this._pageNumber = 0;
            this._pageSize = pageSize;
            this._resultSet = resultSet;
            this._resultList = this._resultSet.ToList<T>();
            this._totalResults = this._resultList.Count;

            // Calculate the total number of pages
            if ((this._totalResults % this._pageSize) > 0)
            {
                this._totalPages = (this._totalResults / this._pageSize) + 1;
            }
            else
            {
                this._totalPages = (this._totalResults / this._pageSize);
            }
        }

        /// <summary>
        /// Returns the pageIndex as specified in the method call.
        /// </summary>
        /// <param name="pageIndex">A 0-based index representing pageIndex number whose results are to be returned.</param>
        /// <returns>The results for the specified pageIndex.</returns>
        /// <exception cref="Inspire.Classes.Exceptions.ResultsOutOfBoundsException">Thrown if the specified pageIndex is out of bounds for the result set.</exception>
        public LinkedList<T> MoveToPage(int pageIndex)
        {
            // Test to ensure that pageIndex falls within the bounds of the result set
            if ((pageIndex < 0) || (pageIndex > this._totalPages))
            {
                throw new ResultsOutOfBoundsException("The requested pageIndex is out of bounds for the current result set. The pageIndex [" + pageIndex.ToString() + "] does not " +
                    "fall between [0] and [" + this._totalPages.ToString() + "].");
            }

            // Update the page number
            this._pageNumber = pageIndex;

            // Update the listPosition
            this._listPosition = pageIndex * this._pageSize;

            // Reset the result list
            this._page.Clear();

            // Iterate through the result set to get the items for the requested page
            while ((this._listPosition < (this._pageSize * (pageIndex + 1))) && (this._listPosition < this._totalResults))
            {
                // If we have entered the group of items for the current page, add each of them
                if (this._listPosition > ((this._pageSize * pageIndex) - 1))
                {
                    this._page.AddLast(this._resultList[this._listPosition]);
                }

                this._listPosition++;
            }

            // Return the results
            return this._page;
        }

        /// <summary>
        /// Convenience method for getting the next page of items.
        /// </summary>
        /// <returns></returns>
        public LinkedList<T> Next()
        {
            return this.MoveToPage(this._pageNumber + 1);
        }

        /// <summary>
        /// Convenience method for getting the previous page of items.
        /// </summary>
        /// <returns></returns>
        public LinkedList<T> Previous()
        {
            return this.MoveToPage(this._pageNumber - 1);
        }

        /// <summary>
        /// Releases resources used by Paginator.
        /// </summary>
        public void Dispose()
        {
            this._resultList.Clear();
            this._resultSet.Dispose();
            this._page.Clear();
        }
    }
}
